package cn.sylinx.horm.dialect.sql;

import cn.sylinx.horm.dialect.fs.FS;
import cn.sylinx.horm.exception.HORMException;
import cn.sylinx.horm.model.cache.ModelCacheUtil;
import cn.sylinx.horm.model.cache.ModelFabric;
import cn.sylinx.horm.util.StrKit;
import cn.sylinx.horm.util.Tuple;

import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

class OracleSqlBuilder extends DefaultSqlBuilder {

    protected String[] getEscapeChar() {
        return new String[]{"\"", "\""};
    }

    @Override
    protected boolean toUpperCase() {
        return true;
    }

    @Override
    public Tuple buildPaginatorSql(String preSql, int pageNumber, int pageSize) {

        // 计算总数sql
        String sqlCount = "SELECT count(1) as totalCount from (" + preSql + ")";

        int offsetBegin = pageSize * (pageNumber - 1);
        int offsetEnd = offsetBegin + pageSize;

        // 查询分页sql
        String sqlLimit = "SELECT /*+ FIRST_ROWS */ * FROM (SELECT A.*, ROWNUM RN FROM (" + preSql
                + ") A  WHERE ROWNUM <= ?) WHERE RN > ? ";
        Object[] params = new Object[]{offsetEnd, offsetBegin};
        return Tuple.apply(sqlCount, sqlLimit, params);
    }

    @Override
    public String buildValidateQuery() {
        return "SELECT 1 FROM DUAL";
    }

    @Override
    public String getTable(Class<?> clz) {
        String table = ModelCacheUtil.getModelFabric(clz).getTableName();
        return table.toUpperCase();
    }

    @Override
    public String getTableColumn(Class<?> clz, String prop) {
        ModelFabric mf = ModelCacheUtil.getModelFabric(clz);
        Map<String, String> attrMap = mf.getAttrMapping();
        String column = attrMap.get(prop);
        if (column == null) {
            column = prop;
        }
        return column.toUpperCase();
    }

    public Tuple buildSelectSQL(FS<?> fluentSql) {

        StringBuilder sql = new StringBuilder();
        if (StrKit.isNotBlank(fluentSql.getHint())) {
            sql.append(fluentSql.getHint()).append(" ");
        }

        sql.append("SELECT");
        if (fluentSql.isDistinct()) {
            sql.append(" DISTINCT");
        }

        String singleField = null;
        Class<?> singleFieldClass = null;

        if (fluentSql.isSingleField()) {

            // 如果只取1列
            if (StrKit.isBlank(fluentSql.getSelectColumns())
                    && StrKit.isBlank(fluentSql.getSelectExpressionColumns())) {
                throw new HORMException("need one column at least");
            }

            singleFieldClass = Object.class;

            if (StrKit.isNotBlank(fluentSql.getSelectColumns())) {
                // 首先使用selectColumns内容, 其次使用 selectExpressionColumns，native sql表达式
                singleField = fluentSql.getSelectColumns().split(",")[0].trim();

                String bingoAttr = null;
                Map<String, String> attrs = fluentSql.getAttrs();
                for (Map.Entry<String, String> entry : attrs.entrySet()) {
                    if (singleField.equals(entry.getKey()) || singleField.equals(entry.getValue())) {
                        bingoAttr = entry.getKey();
                    }
                }

                if (bingoAttr == null) {
                    throw new HORMException("no field specified");
                }

                Map<String, Field> fieldMap = ModelCacheUtil.getModelFabric(fluentSql.getModelClass()).getFieldMap();
                singleFieldClass = fieldMap.get(bingoAttr).getType();

            }

        }

        boolean hasSelected = false;
        if (StrKit.isNotBlank(fluentSql.getSelectColumns())) {
            sql.append(' ').append(fluentSql.getSelectColumns()).append(' ');
            hasSelected = true;
        } else if (fluentSql.getExcludedColumns() != null && !fluentSql.getExcludedColumns().isEmpty()) {
            sql.append(' ').append(buildExcludedColumns(fluentSql.getModelClass(), fluentSql.getExcludedColumns(), true,
                    fluentSql.getPreEscape(), fluentSql.getPostEscape())).append(' ');
            hasSelected = true;
        }

        if (StrKit.isNotBlank(fluentSql.getSelectExpressionColumns())) {
            // 如果表达式不为空
            sql.append(hasSelected ? "," : "").append(" ").append(fluentSql.getSelectExpressionColumns()).append(" ");

        } else if (!hasSelected) {
            sql.append(" * ");
        }

        sql.append("FROM ").append(fluentSql.getTableName());
        if (fluentSql.getConditionSQL().length() > 0) {
            sql.append(" WHERE ").append(fluentSql.getConditionSQL().substring(FS.AND_STR.length()));
        }

        String groupBy = fluentSql.getGroupBySQL().toString();
        if (StrKit.isNotBlank(groupBy)) {
            sql.append(" GROUP BY").append(groupBy);
        }

        String orderBy = fluentSql.getOrderBySQL().toString();
        if (StrKit.isNotBlank(orderBy)) {
            sql.append(" ORDER BY").append(orderBy);
        }

        String limitSql = fluentSql.getLimitSQL().toString();
        if (StrKit.isNotBlank(limitSql)) {
            sql.append(limitSql);
        }

        int len = fluentSql.getParamValues() == null || fluentSql.getParamValues().isEmpty() ? 0
                : fluentSql.getParamValues().size();
        Object[] params = null;

        if (len > 0) {
            params = new Object[len];
            fluentSql.getParamValues().toArray(params);
        }

        return Tuple.apply(sql.toString(), params,
                (singleFieldClass == null ? fluentSql.getModelClass() : singleFieldClass));
    }

    public String buildExcludedColumns(Class<?> clz, List<String> excludedColumns, boolean useSymbol, String preEscape,
                                       String postEscape) {

        ModelFabric mf = ModelCacheUtil.getModelFabric(clz);
        Map<String, String> attrs = mf.getRawAttrMapping();
        Set<String> columns = new HashSet<String>();
        Set<Entry<String, String>> kvsets = attrs.entrySet();
        for (Entry<String, String> entry : kvsets) {
            if (excludedColumns.contains(entry.getKey()) || excludedColumns.contains(entry.getValue())
                    || excludedColumns.contains(entry.getValue().toLowerCase())) {
                continue;
            }
            columns.add(preEscape + entry.getValue().toUpperCase() + postEscape);
        }

        StringBuilder sb = new StringBuilder();
        columns.forEach(k -> sb.append(k).append(","));
        if (sb.length() > 0) {
            sb.deleteCharAt(sb.length() - 1);
        }
        return sb.toString();
    }

}
